﻿namespace Foggy
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel.Composition;
    using System.ComponentModel.Composition.Hosting;
    using System.ComponentModel.Composition.Primitives;
    using System.Data.Entity;
    using System.Data.Entity.Infrastructure;
    using System.IO;
    using System.Linq;
    using Caliburn.Micro;

    using Foggy.Data;
    using Foggy.ViewModels;

    using NLog;

    using LogManager = NLog.LogManager;

    /// <summary>
    /// Foggy bootstrapper
    /// </summary>
    public class FoggyBootstrapper : Bootstrapper<IShellViewModel>
    {
        /// <summary>
        /// The logger instance
        /// </summary>
        private static readonly Logger Logger = LogManager.GetCurrentClassLogger();

        /// <summary>
        /// The container
        /// </summary>
        private CompositionContainer container;

        /// <summary>
        /// Override this to add custom behavior for unhandled exceptions.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The event args.</param>
        protected override void OnUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
        {
            Logger.ErrorException("There was an unhandled exception", e.Exception);
        }

        /// <summary>
        /// Override this to provide an IoC specific implementation.
        /// </summary>
        /// <param name="instance">The instance to perform injection on.</param>
        protected override void BuildUp(object instance)
        {
            this.container.SatisfyImportsOnce(instance);
        }

        /// <summary>
        /// By default, we are configured to use MEF
        /// </summary>
        protected override void Configure()
        {
            this.ConfigureContainer();
            ConfigureEntityFramework();
        }

        /// <summary>
        /// Gets all instances.
        /// </summary>
        /// <param name="serviceType">Type of the service.</param>
        /// <returns>All instances of the service</returns>
        protected override IEnumerable<object> GetAllInstances(Type serviceType)
        {
            return this.container.GetExportedValues<object>(AttributedModelServices.GetContractName(serviceType));
        }

        /// <summary>
        /// Gets the instance.
        /// </summary>
        /// <param name="serviceType">Type of the service.</param>
        /// <param name="key">The service key.</param>
        /// <returns>The service</returns>
        protected override object GetInstance(Type serviceType, string key)
        {
            string contract = string.IsNullOrEmpty(key) ? AttributedModelServices.GetContractName(serviceType) : key;
            var exports = this.container.GetExportedValues<object>(contract);

            if (exports.Count() > 0)
            {
                return exports.First();
            }

            throw new Exception(string.Format("Could not locate any instances of contract {0}.", contract));
        }

        /// <summary>
        /// Creates the directory if it does not exist.
        /// </summary>
        /// <param name="databaseFolder">The database folder.</param>
        private static void CreateDirectoryIfItDoesntExist(string databaseFolder)
        {
            if (Directory.Exists(databaseFolder))
            {
                return;
            }

            try
            {
                Directory.CreateDirectory(databaseFolder);
            }
            catch (Exception exception)
            {
                Logger.ErrorException(string.Format("There was an error creating the directory '{0}'", databaseFolder), exception);
            }
        }

        /// <summary>
        /// Configures entity framework.
        /// </summary>
        private static void ConfigureEntityFramework()
        {
            var localDataFolder = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
            var databaseFolder = string.Format(@"{0}\DevDigital\Foggy", localDataFolder);

            CreateDirectoryIfItDoesntExist(databaseFolder);

            FoggyContext.DataDirectory = databaseFolder;

            // Explicitly set initialiser for clarity
            Database.DefaultConnectionFactory = new SqlCeConnectionFactory("System.Data.SqlServerCe.4.0");
            Database.SetInitializer(new CreateDatabaseIfNotExists<FoggyContext>());

            // Create the database if it doesn't already exist
            using (var context = new FoggyContext())
            {
                context.Database.Initialize(false);
            }
        }

        /// <summary>
        /// Configures the container.
        /// </summary>
        private void ConfigureContainer()
        {
            var catalog = new AggregateCatalog(
                AssemblySource.Instance.Select(x => new AssemblyCatalog(x)).OfType<ComposablePartCatalog>());

            this.container = new CompositionContainer(catalog);

            var batch = new CompositionBatch();

            batch.AddExportedValue<IWindowManager>(new WindowManager());
            batch.AddExportedValue<IEventAggregator>(new EventAggregator());
            batch.AddExportedValue(this.container);
            batch.AddExportedValue(catalog);

            this.container.Compose(batch);
        }
    }
}